package idavollr

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"

	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/client"
	"github.com/emersion/go-message"
)

type AbstractImapMessage interface {
	Section() *imap.BodySectionName
	Message() *imap.Message
	MimeType() string
	SetMessageBytes([]byte)
	MessageBytes() []byte
	SetMimeMessage(*message.Entity)
	MimeMessage() *message.Entity
	SetMessageReader(io.Reader)
	MessageReader() io.Reader
	SetMessageBody([]byte)
	SetClient(*client.Client)
	Client() *client.Client
}

type ImapMessage struct {
	Sect     *imap.BodySectionName
	Msg      *imap.Message
	Mimetype string

	cli      *client.Client
	msgBytes []byte
	mimeMsg  *message.Entity
	bdyRdr   io.Reader
	bdy      []byte
}

func (m ImapMessage) Section() *imap.BodySectionName {
	return m.Sect
}

func (m ImapMessage) Message() *imap.Message {
	return m.Msg
}

func (m ImapMessage) MimeType() string {
	return m.Mimetype
}

func (m *ImapMessage) SetMessageBytes(b []byte) {
	m.msgBytes = b
}

func (m ImapMessage) MessageBytes() []byte {
	return m.msgBytes
}

func (m *ImapMessage) SetMimeMessage(msg *message.Entity) {
	m.mimeMsg = msg
}

func (m ImapMessage) MimeMessage() *message.Entity {
	return m.mimeMsg
}

func (m *ImapMessage) SetMessageReader(r io.Reader) {
	m.bdyRdr = r
}

func (m ImapMessage) MessageReader() io.Reader {
	return m.bdyRdr
}

func (m *ImapMessage) SetMessageBody(b []byte) {
	m.bdy = b
}

func (m ImapMessage) MessageBody() []byte {
	return m.bdy
}

func (m *ImapMessage) SetClient(c *client.Client) {
	m.cli = c
}

func (m ImapMessage) Client() *client.Client {
	return m.cli
}

func ReadMessageBody(m AbstractImapMessage) (AbstractImapMessage, error) {
	r := m.Message().GetBody(m.Section())
	if r == nil {
		return m, MalformedMessageError{
			Cause:     errors.New("no body in message"),
			MessageID: m.Message().Envelope.MessageId,
		}
	}
	messageBytes, err := io.ReadAll(r)
	m.SetMessageBytes(messageBytes)
	return m, err
}

func ParseMimeMessage(m AbstractImapMessage) (AbstractImapMessage, error) {
	r := bytes.NewReader(m.MessageBytes())
	message, err := message.Read(r)
	m.SetMimeMessage(message)
	return m, err
}

func getNextPart(message *message.Entity, ID string, mimetype string) (io.Reader, error) {
	if mr := message.MultipartReader(); mr != nil {
		for {
			p, err := mr.NextPart()
			if err == io.EOF {
				break
			} else if err != nil {
				return nil, fmt.Errorf("while reading next part: %w", err)
			}
			return getNextPart(p, ID, mimetype)
		}
	} else {
		t, _, err := message.Header.ContentType()
		if err != nil {
			return nil, fmt.Errorf("while getting content type: %w", err)
		}
		if t == mimetype {
			return message.Body, nil
		}
	}
	return nil, MalformedMessageError{
		Cause:     errors.New(mimetype + " not found"),
		MessageID: ID,
	}
}

func GetBody(m AbstractImapMessage) (AbstractImapMessage, error) {
	reader, err := getNextPart(m.MimeMessage(), m.Message().Envelope.MessageId, m.MimeType())
	m.SetMessageReader(reader)
	return m, err
}

func ReadBody(m AbstractImapMessage) (AbstractImapMessage, error) {
	body, err := io.ReadAll(m.MessageReader())
	m.SetMessageBody(body)
	return m, err
}

func RecoverMalformedMessage(m AbstractImapMessage, err error) (AbstractImapMessage, error) {
	var malformedMessageError MalformedMessageError
	if errors.As(err, &malformedMessageError) {
		err = nil
		log.Println(malformedMessageError.Error())
		log.Println(string(m.MessageBytes()))
	}
	return m, err
}

func RecoverErroredMessages(m AbstractImapMessage, err error) (AbstractImapMessage, error) {
	log.Printf("message %s errored: %v\n", m.Message().Envelope.MessageId, err)
	return m, nil
}
